/* create.c : create a process
 */

#include <kernel.h>

extern pcb * pcbTable;
extern queueElem * readyQueue;

/* Your code goes here */
/* This is incremented for each new process. Used to asssign unique PIDs. */
unsigned int globalPIDcounter = 0;	

/* Wrapper that handles process returns. Create() sets the EIP to this function, and this function runs the process code. 
 	The process can then return to this wrapper. */
void wrapperProcess( void (*func)() ){
	/* Gets the address of the process code automatically as it checks the stack for the args.
	   It is already in the right spot on the stack, above the retAddr, above EBP. */
	if (SYSCALL_UNIT_TEST){
		kprintf("Executing wrapper process with funcPtr %X. WP addr is %X.\n", func, &wrapperProcess);
		DELAY(SYSCALL_UT_DELAY);
	}
	/* Execute the process code */	
	func();
	/* Process returned. If root returned, we're boned. */
	sysstop();
}

/* Create a process and add it to the readyQueue */
unsigned int create( void (*func)(), unsigned int stackSize, char* process_name){
	/* 	Func points to the start of the process' code. stackSize denotes the requested stack to allocate.
	 	Returns pid on success, or -1 on fail */
	
	/* Allocate a new PCB and add it to the end of the table (which is a linked list) */
	pcb * process = (pcb*) kmalloc(sizeof(pcb));
	if (process == NULL){
		// Allocation failed.
		kprintf("PROCESS CREATION ERROR: Failed to allocate pcb memory!\n");
		return -1;
	}

	/* Update the last table pcb to point to this one. */
	if (pcbTable == NULL){
		/* The table is empty. No processes exist. */
		pcbTable = process;
	}
	else {
		pcb * indexProc = pcbTable;
		while (indexProc->next != NULL){
			indexProc = (pcb*) indexProc->next;
		}
		indexProc->next = (struct pcb*) process;
	}
	
	/* Setup the process address space: (add entries to GDT) */
	/* No need in assignment 1 */
	
	/* 	Allocate stack with kmalloc() */
	uint8* memRegion	= kmalloc(stackSize);
	if (memRegion == NULL){
		// Allocation failed.
		kprintf("PROCESS CREATION ERROR: Failed to allocate process stack memory!\n");
		return -1;
	}
 	uint8* stackAddress = memRegion + stackSize;	/* Directs pointer to the highest addr of the region */
	
	/* Allocate space for PCB name string and copy the chars from the source to the new area */
	process->name = kmalloc(strlen(process_name));
	if (stackAddress == NULL){
		// Allocation failed.
		kprintf("PROCESS CREATION ERROR: Failed to allocate process name memory!\n");
		return -1;
	}
	strcpy(process->name, process_name);
	
	/* Initialize pcb */
	process	->	pid				=	++globalPIDcounter;
	process	->	state			=	NEW;
	process	->	parent_pid		=	NULL; 		/* This is set by the dispatcher, except for in the case of root. */
	process	->	stackPointer	=	stackAddress;
	process ->  memAllocPointer = 	memRegion; 	/* Start of the reserved region is kept so it can be freed. */
	process -> 	next			= 	NULL;
			
	/* 	initialize stack */
	/* All the items that I will work with here are 32 bits wide. */
	uint32* tempStackPointer32  = (uint32*) process->stackPointer;
	
	/* The ordering of these stack placements is based on slide 181 */
	tempStackPointer32			-=  8; /* saftey margin is offset 8 uint32 sizes below the highest allocated addr */
	/* Place the function pointer address inside the safety margin so the wrapper can retrieve it. */
	*tempStackPointer32			=	(uint32)func; /* address of first instruction of process is pushed to stack. Wrapper later reads it as its 'args'. */
	tempStackPointer32			-=  1; /* Accounts for location of EBP */
	*tempStackPointer32			=	0x00000000; /* EBP */
	
	/* Done doing 32bit stack manipulation. Return to byte addressing */
	process->stackPointer 		= (uint8*) tempStackPointer32;
	
	/* Setup initial cpu context to be switched in on first run */
	process->stackPointer		-=	sizeof(cpu); /* In bytes */
	cpu* creationContextFrame 	= (cpu*) process->stackPointer;
	creationContextFrame->edi	= 0x00000000;
	creationContextFrame->esi	= 0x00000000;
	creationContextFrame->ebp	= 0x00000000;
	creationContextFrame->esp	= 0x00000000;
	creationContextFrame->ebx	= 0x00000000;
	creationContextFrame->edx	= 0x00000000;
	creationContextFrame->ecx	= 0x00000000;
	creationContextFrame->eax	= 0x00000000;
	creationContextFrame->eip	= (uint32) &wrapperProcess;
	creationContextFrame->cs	= getCS();
	creationContextFrame->eflags= 0x00003200;	/* I hope this is fine */
	
	if (SYSCALL_UNIT_TEST){
		kprintf("Created process with funcPtr %X. WP addr is %X. Name is %s.\n", func, &wrapperProcess, process_name);
		if (SYSCALL_UT_SHOW_STACK){
			kprintf(" Stack centered at EBP.\n");
			printStackPointerArea((process->stackPointer + sizeof(cpu)), 2, 4);
		}
		DELAY(SYSCALL_UT_DELAY);
		kprintf("\n");
	}
	
	/* Now when the context switcher loads this process's stackPointer, it will do popa, iret, and 
		load the process into the CPU! */
	
	/* Place the pcb on the ready queue */
	verbose("New process %d '%s' created. fPtr %X, stackSize %X.\n", 
					process->pid, process_name, func, stackSize);
	ready(process);
	return process->pid;
}

/*	Free the resources and set the process to DEAD in the pcb table.
	Remove it from the table and the RQ. Free allocated memory. */
int cleanup( pcb * process){
	/* For safety, the root process is unkillable. */
	verbose("Process %d set for termination...", process->pid);
	if (process->parent_pid == NULL){
		kprintf("\nERROR: The root process is unkillable. It's made of pure DOLEMITE!\n");
		return FALSE;
	}
	process->state = DEAD;	
	
	/* Remove pcb from pcb table */
	pcb * indexProc = pcbTable;
	/* Check if it is the first entry */
	if (pcbTable == process ){
		pcbTable = (pcb*) process -> next;
	}
	else {
		/* Find it on the PCB table, remove it from the previous entry's 'next' pointer. */
		while ((pcb*)indexProc->next != (pcb*)process){
			indexProc = (pcb*) indexProc->next;
		}
		indexProc->next = process->next;
	}
	
	/* Remove process from any queue elements. It is certainly possible that it is not in any queues. */
	queueElem * freeThisElem = NULL;
	/* Check if it is the first entry */
	if (readyQueue->processPointer == process ){
		freeThisElem 	= readyQueue;
		readyQueue		= (queueElem*) readyQueue -> next; /* Set to NULL if this is the only entry. */
	}
	else {
		queueElem * previousElem = readyQueue;
		queueElem * processCheckElem;
		pcb* checkThisProc;
		/* Find it on the queue, remove it from the previous entry's 'next' pointer. */
		while (processCheckElem != NULL){
			processCheckElem 	= (queueElem*) previousElem->next;
			checkThisProc 		= processCheckElem -> processPointer;
			if (checkThisProc != process){
				/* Check the next one */
				previousElem 	= processCheckElem;
			}
			else {
				/* Index elem's next pointer points to the elem that holds the process we are cleaning up */
				freeThisElem 	= processCheckElem;
				/* Update the previous elem's pointer to jump over the deleted elem */
				previousElem->next = processCheckElem->next;
				break;
			}
		}
	}
	
	/* Free the queue elements, if found. */
	if (freeThisElem != NULL) kfree(freeThisElem);
	
	/* Free the stack for the process */
	kfree(process->memAllocPointer);
	
	/* Free the pcb */
	kfree(process->name);
	kfree(process);
	
	verbose("Process termination complete.\n", process->pid);
	return TRUE;
}
/* =================== UNIT TEST PRINT FUNCTIONS ==================== */


